package cscie160.project.bank;
import java.util.HashMap;
import java.util.ArrayList;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import cscie160.project.atm.*;
import cscie160.project.bank.security.Security;
/**
 * This class manages the simplest demo operations with the ATM on server side.
 * @author Michael Melnikov 
*/
public class BankServerImpl extends UnicastRemoteObject implements BankServer
{
    /**
    * BankServer name
    */
    private String name;
    
    /**
    * set of accounts to be mainteined
    */
    private HashMap <Integer, Account> accounts;
    
    /**
    * account PIN to start with according to the assignment
    */
    private final static int minPin = 1234;
   
    /**
    * Bank security Service
    */   
    private Security securityService;
    
    /**
     * main method to collect command line input parameters and launch the server side operations.
     * @param args String[]
    */ 
    public static void main(String args[])
    {
        //Create and install a security manager
        System.setSecurityManager(new RMISecurityManager());
        BankServerImpl bankServer = null;
        String bankName = "JavaRmiBank";
        ArrayList<Integer> balances = new ArrayList<Integer>();
        balances.add(0);
        balances.add(100);
        balances.add(500);
        
        int bankServerPort = 1099; //50001;
        if (args.length > 0 && !(args[0].equals("?") || args[0].equalsIgnoreCase("help")))
        {
            try
            {
                bankServerPort = Integer.parseInt(args[0]);
            } catch (NumberFormatException nfex)
            {
                System.out.println("Provided argument\"" + args[0] + "\" can't be a port");
                return;
            }
        }
        
        try
        {
            java.rmi.registry.Registry registry = 
                java.rmi.registry.LocateRegistry.createRegistry(bankServerPort);
            bankServer = new BankServerImpl(balances, bankName);
            System.out.println("BankServer created. hashCode=" + bankServer.hashCode());
            
            String serverRebindUrl = "//localhost:" + Integer.toString(bankServerPort) + "/bankserver";
            Naming.rebind(serverRebindUrl, bankServer);
            System.out.println("BankServer bound in registry on port " + Integer.toString(bankServerPort));
        } catch (Exception e)
        {
            System.out.println("Server err: " + e.getMessage());
            e.printStackTrace();
        }
    }
	
    /**
    * no-args constructor creates BankServerImpl with no accounts
    * @param name String
    */    
    public BankServerImpl(String name) throws RemoteException 
    {
        super();
        this.name = name;
    }
 
    /**
    * 2-args constructor creates BankServerImpl with single account with balance equal to its argument
    * @param balance int
    * @param name String
    */   
    public BankServerImpl(int balance, String name) throws ATMException, RemoteException
    {
        this(new ArrayList<Integer>(java.util.Arrays.asList(
            new Integer[]{new Integer(balance)})), name);
    }   
    
    /**
    * 2-args constructor creates BankServerImpl with few accounts.
    * Accounts have balances equal to values
     * of ArrayList passed as constructor argument
    * @param balances ArrayList<Integer> 
    * @param name String
    */   
    public BankServerImpl(ArrayList<Integer> balances, String name) throws ATMException, RemoteException
    {
        this(name);
        if (balances == null || balances.size() == 0)
            throw new ATMException("no balances provided.");        
        accounts = new HashMap<Integer, Account>();
        HashMap<Integer, Integer> authorizedAccounts = new HashMap<Integer, Integer>();
        
        for (int balanceNum = 0; balanceNum < balances.size(); balanceNum++)
        {
            int accountNum = accounts.size() + Account.getMinAccountNumber();
            int pin = obtainPin(balanceNum);
            AccountInfo accountInfo = new AccountInfo(accountNum, pin);
            Account account = new Account(accountInfo);
            accounts.put(accountNum, account);
            authorizedAccounts.put(accountNum, pin);
            account.deposit(balances.get(balanceNum));
        }
        securityService = new Security(authorizedAccounts);
        //System.out.println(" BankServerImpl constructor: created accounts=" + accounts + "; securityService=" +securityService); 
    }   
       
    /**
    * checks the authenticity of provided information
    * @param aInfo AccountInfo
    * @return boolean
    */    
    public boolean isAuthentic(AccountInfo aInfo) throws RemoteException
    {
        if (securityService == null)
            return false;
        boolean isAuthentic = securityService.isAuthentic(aInfo);
        return isAuthentic;    
    }

    /**
    * checks the permission on required operation
    * @param aInfo AccountInfo
    * @param command Commands
    * @return boolean
    */    
    public boolean isPermitted(AccountInfo aInfo, Commands command) throws RemoteException
    {
        if (securityService == null)
            return false;
        boolean isPermitted = securityService.isPermitted(aInfo, command);
        return isPermitted; 
    }
    
    /**
    * deposits sum equal to its argument to the account
    * @param accountInfo AccountInfo    
    * @param amount int
    */
    public void deposit(AccountInfo accountInfo, int amount) throws ATMException, RemoteException
    {
        Account account = getAccount(accountInfo.getAccountNumber());
        if (account == null)
            throw new ATMException(getClass().getName() + ".deposit(): account with ID=" +
                accountInfo.getAccountNumber() + " doesn't exist.");
        account.deposit(amount);
    }
    
    /**
    * withdraws sum equal to its argument off the account
    * @param accountInfo AccountInfo    
    * @param amount int    
    */    
    public void withdraw(AccountInfo accountInfo, int amount) throws ATMException, RemoteException
    {
        Account account = getAccount(accountInfo.getAccountNumber());    
        if (account == null)
            throw new ATMException(getClass().getName() + ".withdraw(): account with ID=" +
                accountInfo.getAccountNumber() + " doesn't exist.");
        account.withdraw(amount);  
    }
    
	/**
	* returns the balance for certain account
    * @param accountInfo AccountInfo
	* @return int
	*/
	public int getBalance(AccountInfo accountInfo) throws RemoteException, ATMException
    {
        Account account = getAccount(accountInfo.getAccountNumber());    
        if (account == null)
            throw new ATMException(getClass().getName() + ".getBalance(): account with ID=" +
                accountInfo.getAccountNumber() + ", PIN=" + accountInfo.getPin() + " doesn't exist.");
        return account.getBalance();
    }
    
    /**
    * returns Account by its integer id
    * @param accountNumber int      
    * @return cscie160.project.account.Account    
    */    
    private Account getAccount(int accountNumber) throws RemoteException
    {
        if (!accounts.containsKey(accountNumber))
            return null;
        return accounts.get(accountNumber);     
    }	
    
    /**
    * creates account PIN according to assignment rules
    * @param balanceNumber int
    * @return int
    */    
    private int obtainPin(int balanceNumber)
    {
        if (balanceNumber == 0)
        {
            return minPin;
        }
        int thousands = minPin / 1000 ;
        int hundreds = (minPin - thousands * 1000) / 100;
        int dozens = (minPin - thousands * 1000 - hundreds * 100) / 10;
        int units = minPin - thousands * 1000 - hundreds * 100 - dozens * 10;
        
        thousands += balanceNumber;
        hundreds += balanceNumber;
        dozens += balanceNumber;
        units += balanceNumber;
        int pin = thousands *1000 + hundreds * 100 + dozens * 10 + units;        
        return pin;
    }
}
